package club.cearnach.api.service.impl;

import club.cearnach.api.annotation.CacheMovieDTOSave;
import club.cearnach.api.annotation.CacheMovieDelete;
import club.cearnach.api.annotation.CacheMovieSave;
import club.cearnach.api.dao.IMovieDao;
import club.cearnach.api.dto.HotMovieDTO;
import club.cearnach.api.dto.MovieDTO;
import club.cearnach.api.exception.*;
import club.cearnach.api.form.PageBean;
import club.cearnach.api.service.*;
import club.cearnach.api.util.FileUtil;
import club.cearnach.api.util.GlobalUtil;
import club.cearnach.core.property.MovieWebProperties;
import club.cearnach.entity.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * @author 阮胜
 * @date 2018/5/4 19:41
 */
@Service
@CacheConfig(cacheNames = "CACHE_MOVIE_ENTITY")
@Transactional(rollbackFor = Exception.class)
@Slf4j
public class MovieServiceImpl implements IMovieService {
    public static final String HOT_MOVIE_ZSET = "hotMovieZSet";
    public static final String CACHE_MOVIE_NAME = "CACHE_MOVIE";
    public static final String CACHE_MOVIE_FIND_ALL = "CACHE_FIND_ALL";
    public static final String CACHE_MOVIE_PAGE_BEAN = "#pageBean.getPage()+'_'+#pageBean.getSize()";
    private final IMovieDao movieDao;
    private final IActorService actorService;
    private final IDirectorService directorService;
    private final StringRedisTemplate stringRedisTemplate;
    /**
     * 这里为了避免循环依赖,不能使用构造函数的方式注入,需要使用使用注解注入
     */
    @Autowired
    private IMovieTypeService movieTypeService;
    @Autowired
    private IMovieLanguageService movieLanguageService;
    @Autowired
    private MovieWebProperties movieWebProperties;

    public MovieServiceImpl(IMovieDao movieDao, IActorService actorService, IDirectorService directorService, StringRedisTemplate stringRedisTemplate) {
        this.movieDao = movieDao;
        this.actorService = actorService;
        this.directorService = directorService;
        this.stringRedisTemplate = stringRedisTemplate;
    }


    @Override
    public Page<Movie> findAll(Pageable pageable) {
        return movieDao.findAll(pageable);
    }

    @CacheMovieSave
    @Override
    public Movie save(Movie movie) {
        return movieDao.save(movie);
    }

    @CacheMovieDTOSave
    @Override
    public Movie save(MovieDTO movieDTO) throws MovieTypeException, MovieLanguageException, MovieNotFoundException, MoviePathException, IOException {
        Movie movie = null;
        Integer id = movieDTO.getId();
        if (id != null && id != 0) {
            movie = movieDao.findById(movieDTO.getId()).orElseThrow(() -> new MovieNotFoundException(MovieNotFoundException.NOT_FOUND));
        }
        if (movie == null) {
            movie = new Movie();
        } else {
            movieDTO.setId(movie.getId());
        }
        BeanUtils.copyProperties(movieDTO, movie);

        MultipartFile imageFile = movieDTO.getImageFile();
        if (imageFile != null) {
            String imageFileId = FileUtil.saveFile(imageFile, movieWebProperties.getUpload().getMovieImagePath());
            movie.setImageFileName(imageFileId);
        }
        MultipartFile videoFile = movieDTO.getVideoFile();
        if (videoFile != null) {
            String videoId = FileUtil.saveFile(videoFile, movieWebProperties.getUpload().getMovieVideoPath());
            movie.setVideoFileName(videoId);
        }
        MovieType type = movieTypeService.findById(movieDTO.getTypeId());
        MovieLanguage language = movieLanguageService.findById(movieDTO.getLanguageId());
        HashSet<Actor> actorSet = actorService.saveMany(movieDTO.getActors());
        HashSet<Director> directorSet = directorService.saveMany(movieDTO.getDirectors());
        movie.setMovieLanguage(language);
        movie.setType(type);
        movie.setActors(actorSet);
        movie.setDirectors(directorSet);
        movieDao.save(movie);
        return movie;
    }

    @Override
    public List<Movie> findByName(String name) {
        return movieDao.findByName(name);
    }


    @Override
    public Movie findByNameOne(String name) {
        List<Movie> movieList = movieDao.findByName(name);
        if (CollectionUtils.isEmpty(movieList)) {
            return null;
        }
        return movieList.get(0);
    }

    @Cacheable(value = CACHE_MOVIE_NAME, key = "'movie_'+#id")
    @Override
    public Movie findById(Integer id) throws MovieNotFoundException {
        return movieDao.findById(id).orElseThrow(MovieNotFoundException::new);
    }

    @Override
    public List<Movie> findByTypeId(Integer id) {
        return movieDao.findByTypeId(id);
    }

    @Override
    public List<Movie> findByLanguageId(Integer id) {
        return movieDao.findByMovieLanguageId(id);
    }

    @CacheMovieDelete
    @Override
    public void delete(Integer id) throws MovieException {
        movieDao.findById(id).orElseThrow(MovieNotFoundException::new);
        movieDao.deleteById(id);
        ZSetOperations<String, String> ops = stringRedisTemplate.opsForZSet();
        ops.remove(HOT_MOVIE_ZSET, id.toString());

    }

    @CacheMovieDelete
    @Override
    public long deleteIn(Integer[] idArr) {
        long result = 0;
        for (int id : idArr) {
            try {
                movieDao.deleteById(id);
                result++;
            } catch (Exception e) {
                log.error(e.getMessage());
                e.printStackTrace();
            }
        }
        return result;
    }


    @Cacheable(value = CACHE_MOVIE_FIND_ALL, key = "'initialReleaseDate_'+" + CACHE_MOVIE_PAGE_BEAN)
    @Override
    public Page<Movie> findAllOrderByInitialReleaseDate(PageBean pageBean) {
        return movieDao.findAll(PageRequest.of(pageBean.getPage(), pageBean.getSize(), Sort.by(Sort.Direction.DESC, "initialReleaseDate")));
    }

    @Cacheable(value = CACHE_MOVIE_FIND_ALL, key = "'initialReleaseDate_'+" + CACHE_MOVIE_PAGE_BEAN)
    @Override
    public Page<Movie> findAllOrderByInitialReleaseDateDesc(PageBean pageBean) {
        return movieDao.findAllByOrderByInitialReleaseDateDesc(PageRequest.of(pageBean.getPage(), pageBean.getSize()));
    }

    @Cacheable(value = CACHE_MOVIE_FIND_ALL, key = "'createDate_'+" + CACHE_MOVIE_PAGE_BEAN)
    @Override
    public Page<Movie> findAllOrderByCreateDate(PageBean pageBean) {
        return movieDao.findAll(PageRequest.of(pageBean.getPage(), pageBean.getSize(), Sort.by(Sort.Direction.DESC, "createDate")));
    }

    @Cacheable(value = CACHE_MOVIE_FIND_ALL, key = "'createDate_'+" + CACHE_MOVIE_PAGE_BEAN)
    @Override
    public Page<Movie> findAllOrderByCreateDateDesc(PageBean pageBean) {
        return movieDao.findAllByOrderByCreateDateDesc(PageRequest.of(pageBean.getPage(), pageBean.getSize()));
    }


    @Cacheable(value = CACHE_MOVIE_FIND_ALL, key = "'rate_'+" + CACHE_MOVIE_PAGE_BEAN)
    @Override
    public Page<Movie> findAllOrderByRate(PageBean pageBean) {
        return movieDao.findAll(PageRequest.of(pageBean.getPage(), pageBean.getSize(), Sort.by(Sort.Direction.DESC, "rate")));
    }

    @Cacheable(value = CACHE_MOVIE_FIND_ALL, key = "'rate_'+" + CACHE_MOVIE_PAGE_BEAN)
    @Override
    public Page<Movie> findAllOrderByRateDesc(PageBean pageBean) {
        return movieDao.findAllByOrderByRateDesc(PageRequest.of(pageBean.getPage(), pageBean.getSize()));
    }

    @Override
    public long count() {
        return movieDao.count();
    }

    @Override
    public List<HotMovieDTO> findHotMovie(Integer count) {
        ZSetOperations<String, String> ops = stringRedisTemplate.opsForZSet();
        Set<ZSetOperations.TypedTuple<String>> hotBlogSet = ops.reverseRangeWithScores(HOT_MOVIE_ZSET, 0, count);
        return hotBlogSet.stream()
                .map(stringTypedTuple -> {
                    int movieId = Integer.parseInt(stringTypedTuple.getValue());
                    Movie movie = null;
                    try {
                        movie = findById(movieId);
                    } catch (MovieNotFoundException e) {
                        e.printStackTrace();
                    }
                    return movie == null ? null : new HotMovieDTO(movie, stringTypedTuple.getScore().intValue());
                })
                .collect(Collectors.toList());
    }

    @Override
    public Integer findMovieHot(Integer movieId) {
        ZSetOperations<String, String> ops = stringRedisTemplate.opsForZSet();
        return ops.score(HOT_MOVIE_ZSET, movieId).intValue();
    }

    @Override
    public void incrementMovieHot(Integer movieId) {
        ZSetOperations<String, String> ops = stringRedisTemplate.opsForZSet();
        ops.incrementScore(HOT_MOVIE_ZSET, String.valueOf(movieId), 1);
    }

    @Override
    public Long countMovieByTypeId(Integer id) {
        return movieDao.countMovieByTypeId(id);
    }

    @Override
    public Page<Movie> findAllByTypeIdOrderByCreateDateDesc(Integer typeId, PageBean pageBean) {
        return movieDao.findAllByTypeIdOrderByCreateDateDesc(typeId, PageRequest.of(pageBean.getPage(), pageBean.getSize()));
    }

    @Override
    public Page<Movie> findAllByCondition(String movieName, Integer typeId, Float rate, Integer startYear, Integer endYear, PageBean pageBean) {
        return movieDao.findAllByCondition(movieName, typeId, rate, startYear, endYear,
                PageRequest.of(pageBean.getPage(), pageBean.getSize()));
    }
}
